#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <stdbool.h>

#define MAX_LINE_LENGTH 512

struct globalArgs_t 
{
	bool caseInsensitive;			/* -i option */
	char **inputFiles;			/* input files */
	int numInputFiles;			/* # of input files */
} globalArgs;

static const char *optString = "iVh?";

static const struct option longOpts[] = 
{
	{ "ignore-case", no_argument, NULL, 'i' },
	{ "Version", no_argument, NULL, 'V' },
	{ "help", no_argument, NULL, 'h' },
	{ NULL, no_argument, NULL, 0 }
};

typedef struct 
{
    char* id;
	char* value;
	char* idcmp;
	bool found;
} Registry;

void display_usage()
{
	puts( "-i, --ignore-case ignore differences in case when comparing fields\n"
          "-h, --help display this help and exit\n"
          "-V, --version output version information and exit\n" );
	exit( EXIT_FAILURE );
}

void display_version()
{
     puts("File Joiner - Version 1.0");
     exit( EXIT_FAILURE );
}

void display_order_warning(int fileNumber)
{
    fprintf(stderr, "File Joiner: file %d is not in sorted order\n", fileNumber);
}

void display_repeated()
{
    fprintf(stderr, "File Joiner: file 2 has repeated keys\n");
}

void display_not_found(char* key)
{
    fprintf(stderr, "File Joiner: key '%s' not found in file 2. \
If the key is present, check that there are no sort errors.\n"
		, key);
}

Registry parse_line(char* line)
{
	Registry reg;
	int i;
	size_t keyLength = strcspn(line, " ");
	size_t lineLength = strlen(line);
	
	if (keyLength == lineLength)
	{
	    // no spaces were found, as with join its all key
	    reg.id = (char*) malloc(sizeof(char) * lineLength);
	    reg.idcmp = malloc(sizeof(char*) * (keyLength + 1));
        memset(reg.id, 0, lineLength);
        memset(reg.idcmp, 0, lineLength);
        strncpy(reg.id, line, lineLength - 1);
        reg.value = (char*) malloc(sizeof(char));
        reg.value[0] = '\0';
	}
	else
	{
        reg.id = (char*) malloc(sizeof(char) * (keyLength + 1));
        reg.idcmp = (char*) malloc(sizeof(char) * (keyLength + 1));
        memset(reg.id, 0, keyLength + 1);
        memset(reg.idcmp, 0, keyLength + 1);
        strncpy(reg.id, line, keyLength);

        size_t lineLength = strcspn(line, "\n");
        size_t valueLength = lineLength - keyLength;
        reg.value = (char*) malloc(sizeof(char) * valueLength);
        memset(reg.value, 0, valueLength);
        strncpy(reg.value, line + (keyLength + 1), valueLength - 1);	
	}
	
	if (globalArgs.caseInsensitive)
	{
		for (i = 0; i < strlen(reg.id); i++)
		{
			reg.idcmp[i] = tolower(reg.id[i]);
		}
	}
	else
	{
	    strcpy(reg.idcmp, reg.id);
	}
	
	reg.found = false;
	
	return reg;
}

void free_registry(Registry reg)
{
    free(reg.id);
    free(reg.value);
    free(reg.idcmp);
}

void copy_registry_and_free(Registry* reg, Registry* aux)
{
    char* tempId = reg->id;
    char* tempValue = reg->value;
    char* tempIdCmp = reg->idcmp;
    reg->id = aux->id;
    reg->value = aux->value;
    reg->idcmp = aux->idcmp;
    free(tempId);
    free(tempValue);
    free(tempIdCmp);
    //clearing found value
    reg->found = false;
}

int main(int argc, char *argv[])
{
    globalArgs.caseInsensitive = false;		
	globalArgs.inputFiles = NULL;
	globalArgs.numInputFiles = 0;
	int opt = 0;
	int longIndex = 0;
 
    opterr = 0;

    opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
	while( opt != -1 ) 
	{
		switch( opt ) 
		{
			case 'i':
				globalArgs.caseInsensitive = true;
				break;
				
			case 'V':
				display_version();
				break;
			case 'h':
			case '?':
				display_usage();
				break;
			default:
				/* You won't actually get here. */
				break;
		}
		opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
	}
	
	globalArgs.inputFiles = argv + optind;
	globalArgs.numInputFiles = argc - optind;
	
	if (globalArgs.numInputFiles != 2 || (strcmp(globalArgs.inputFiles[0], "-") == 0 && strcmp(globalArgs.inputFiles[1], "-") == 0))
	{
       display_usage();
	}
			
	//Reading..
	FILE *ifpA, *ifpB;
	bool usingStdIn = true;

    //file numbers
	int aFileNumber = 1;
	int bFileNumber = 2;
	
	//repeated keys in file 2
	bool repeatedKeys = false;
	
	// if one of the files is "-" read from stdin
	if (strcmp(globalArgs.inputFiles[0], "-") == 0)
	{
	    ifpA = stdin;
    	ifpB = fopen(globalArgs.inputFiles[1], "r");
	}
	else if (strcmp(globalArgs.inputFiles[1], "-") == 0)
	{
	    ifpA = fopen(globalArgs.inputFiles[0], "r");
        ifpB = stdin;
	    
	    aFileNumber = 1;
	    bFileNumber = 2;
	}
	else
	{
        usingStdIn = false;
	    ifpA = fopen(globalArgs.inputFiles[0], "r");
	    ifpB = fopen(globalArgs.inputFiles[1], "r");
	}
	
	if (ifpA == NULL)
	{
		printf("File %s not found.\n",globalArgs.inputFiles[0]);
		return (EXIT_FAILURE);
	}
	if (ifpB == NULL)
	{
		printf("File %s not found.\n",globalArgs.inputFiles[1]);
		return (EXIT_FAILURE);
	}
	
	char lineA[MAX_LINE_LENGTH];
	char lineB[MAX_LINE_LENGTH];

    // continue by default
    bool shouldContinue = true;
    
    // order warnings
    bool oneWarned = false;
    bool twoWarned = false;
    
    //position in file B
    long int bFilePosition = 0;
    
    Registry regA;
    Registry regB;
    if (fgets(lineA, MAX_LINE_LENGTH, ifpA) == NULL || fgets(lineB, MAX_LINE_LENGTH, ifpB) == NULL)
    {
        shouldContinue = false;
    }
    else
    {
        regA = parse_line(lineA);
        regB = parse_line(lineB);
    }
    
	while (shouldContinue)
	{          
		if(regA.id != NULL && regB.id != NULL)
	    {	    
            // can process
            int cmpResult = strcmp(regA.idcmp, regB.idcmp);
            if (cmpResult == 0)
	        {
	            regA.found = true;
	        
	            int times = 0;
	            // print until value in B is different
	            // then go back as join does
	            do
	            {
	                regB.found = true;
    	            printf("%s %s %s \n", regA.id, regA.value, regB.value);
    	            times++;
    	            if (fgets(lineB, MAX_LINE_LENGTH, ifpB) == NULL)
    	            {
    	                 break;    
    	            }
    	            
    	            Registry auxB = parse_line(lineB);    	            
                    if (strcmp(regB.idcmp, auxB.idcmp) > 0 && !twoWarned)
                    {
                        // wrong order
                        display_order_warning(bFileNumber);
                        twoWarned = true;
                    }

    	            copy_registry_and_free(&regB, &auxB);
	            } while (strcmp(regA.idcmp, regB.idcmp) == 0);           
                
                if (times > 1 && !repeatedKeys)
                {
                    repeatedKeys = true;
                    display_repeated();
                }
                
	            if (fgets(lineA, MAX_LINE_LENGTH, ifpA) == NULL)
                {
                    shouldContinue = false;
    	            free_registry(regA);
    	            free_registry(regB);
                }
                else
                {
                    Registry auxA = parse_line(lineA);
                    
                    cmpResult = strcmp(regA.idcmp, auxA.idcmp);
                    
                    if (cmpResult == 0)
                    {
                        // seek to position where it started to print
                        fseek(ifpB, bFilePosition, SEEK_SET);
	                    fgets(lineB, MAX_LINE_LENGTH, ifpB);
	                    //restore registry original value
	                    Registry auxB = parse_line(lineB);

                        copy_registry_and_free(&regB, &auxB);
                    }
                    
                    if (cmpResult > 0 && !oneWarned)
                    {
                        // wrong order
                        if (strcmp(globalArgs.inputFiles[1], "-") == 0)
							display_order_warning(bFileNumber);

                        
                        else display_order_warning(aFileNumber);
                        
                        oneWarned = true;
                    }

                    copy_registry_and_free(&regA, &auxA);
                }
                
                if (feof(ifpB))
                {
                    shouldContinue = false;
                }
	        }
	        else if (cmpResult > 0)
	        {
	            // A is greater, read from B
	            bFilePosition = ftell(ifpB);
	            
                if (fgets(lineB, MAX_LINE_LENGTH, ifpB) == NULL)
                {
                    if (!regA.found)
                    {
                        display_not_found(regA.id);
                    }
                    shouldContinue = false;
    	            free_registry(regB);
                }
                else
                {
                    Registry auxB = parse_line(lineB);
                    
                    cmpResult = strcmp(regB.idcmp, auxB.idcmp);
                    
                    if (cmpResult == 0)
                    {
                        if(!repeatedKeys)
                        {
                            repeatedKeys = true;
                            display_repeated();
                        }
                    }
                    
                    if (cmpResult > 0 && !twoWarned)
                    {
                        // wrong order
                        display_order_warning(bFileNumber);
                        twoWarned = true;
                    }

                    copy_registry_and_free(&regB, &auxB);
                }
	        }
	        else
	        {
                if (!regA.found)
                {
                    display_not_found(regA.id);
                }
	        
                // B is greater, read from A
                if (fgets(lineA, MAX_LINE_LENGTH, ifpA) == NULL)
                {
                    shouldContinue = false;
    	            free_registry(regA);
                }
                else
                {
                    Registry auxA = parse_line(lineA);
                    
                    if (strcmp(regA.idcmp, auxA.idcmp) > 0 && !oneWarned)
                    {
                        // wrong order
                        if (strcmp(globalArgs.inputFiles[1], "-") == 0)
							display_order_warning(bFileNumber);
                        
                        else display_order_warning(aFileNumber);
         
                        oneWarned = true;
                    }

                    copy_registry_and_free(&regA, &auxA);
                }
	        }
	    }
	    else
	    {
	        // no spaces found in line => no id
           shouldContinue = false;
           free_registry(regA);
           free_registry(regB);
	    }
	}
	fclose(ifpA);
    fclose(ifpB);
    return 0;
}
